{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 自主避障-数据收集\n",
    "\n",
    "如果您看过\"jetbot基本运动\"演示，那么希望您会喜欢使Jetbot到处移动是多么容易！太酷了！但是，更酷的是，使JetBot可以自行移动！\n",
    "\n",
    "这是一项非常艰巨的任务，它有许多不同的方法，但是通常整个问题都分解为更简单的子问题。可以说，其中之一是\n",
    "要解决的重要子问题是防止机器人进入危险状况的问题！我们称之为“避免碰撞”。\n",
    "\n",
    "在这组笔记本中，我们将尝试使用深度学习和一个非常通用的传感器：相机来解决问题。您将看到如何使用神经网络，摄像头和NVIDIA Jetson Nano，我们可以教机器人一个非常有用的行为！\n",
    "\n",
    "我们采取的避免碰撞的方法是在机器人周围创建一个虚拟的“安全泡沫”。在此安全气泡内，机器人可以绕圈旋转而不会撞到任何物体（或其他危险情况，例如从壁架上掉下来）。\n",
    "\n",
    "\n",
    "当然，机器人会受到其视野的限制，我们不能阻止将物体放置在机器人后面等。但是，我们可以防止机器人自己进入这些场景。\n",
    "\n",
    "我们执行此操作的方法非常简单：\n",
    "\n",
    "首先，我们将手动将机器人放置在违反“安全泡沫”的场景中，并将这些场景标记为“阻塞的（blocked）”。我们将与该标签一起保存机器人看到的快照。\n",
    "\n",
    "其次，我们将手动将机器人放置在可以安全向前移动的场景中，并将这些场景标记为“空旷的（free）”。同样，我们将快照和该标签一起保存。\n",
    "\n",
    "这就是我们在笔记本中要做的所有事情；数据采集​​。一旦拥有大量图像和标签，我们便会将这些数据上传到启用GPU的机器上，在那里我们将对神经网络进行“训练”，以根据看到的图像来预测机器人的安全气泡是否受到侵犯。最后，我们将使用它来实现一个简单的避免碰撞行为:)\n",
    "\n",
    ">重要说明：当JetBot旋转到位时，它实际上围绕两个轮子之间的中心旋转，而不是机器人机箱本身的中心旋转。当您要估计是否违反了机器人的安全泡沫时，这是要记住的重要细节。但是不用担心，您不必一定要精确。如有疑问，最好还是持谨慎态度（一个大的安全泡沫）。我们要确保JetBot不会进入无法通过就位解决的情况。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 显示实时摄像机\n",
    "\n",
    "因此，让我们开始吧。 首先，让我们像在* teleoperation *笔记本中一样初始化和显示相机。\n",
    "\n",
    ">我们的神经网络将224x224像素的图像作为输入。 我们将相机设置为该大小，以最大程度地减少数据集的文件大小（我们已经测试了它适用于此任务）。\n",
    ">在某些情况下，最好以较大的图像尺寸收集数据，然后再缩小为所需的尺寸。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import traitlets\n",
    "import ipywidgets.widgets as widgets\n",
    "from IPython.display import display\n",
    "from jetbot import Camera, bgr8_to_jpeg\n",
    "\n",
    "camera = Camera.instance(width=224, height=224)\n",
    "\n",
    "image = widgets.Image(format='jpeg', width=224, height=224)  # this width and height doesn't necessarily have to match the camera\n",
    "\n",
    "camera_link = traitlets.dlink((camera, 'value'), (image, 'value'), transform=bgr8_to_jpeg)\n",
    "\n",
    "display(image)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "太棒了，接下来让我们创建一些目录，以存储所有数据。 我们将创建一个文件夹“ dataset”，其中将包含两个子文件夹“ free”和“ blocked”，\n",
    "我们将在每种情况下放置图像的位置。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "blocked_dir = 'dataset/blocked'\n",
    "free_dir = 'dataset/free'\n",
    "\n",
    "# we have this \"try/except\" statement because these next functions can throw an error if the directories exist already\n",
    "try:\n",
    "    os.makedirs(free_dir)\n",
    "    os.makedirs(blocked_dir)\n",
    "except FileExistsError:\n",
    "    print('Directories not created becasue they already exist')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果刷新左侧的Jupyter文件浏览器，现在应该看到这些目录出现。 接下来，让我们创建并显示一些用于保存快照的按钮\n",
    "对于每个班级标签。 我们还将添加一些文本框，以显示到目前为止我们收集的每个类别的图像数量。 这很有用，因为我们要\n",
    "确保我们收集的“free”图像与“blocked”图像一样多。 这也有助于了解我们总共收集了多少张图像。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "button_layout = widgets.Layout(width='128px', height='64px')\n",
    "free_button = widgets.Button(description='add free', button_style='success', layout=button_layout)\n",
    "blocked_button = widgets.Button(description='add blocked', button_style='danger', layout=button_layout)\n",
    "free_count = widgets.IntText(layout=button_layout, value=len(os.listdir(free_dir)))\n",
    "blocked_count = widgets.IntText(layout=button_layout, value=len(os.listdir(blocked_dir)))\n",
    "\n",
    "display(widgets.HBox([free_count, free_button]))\n",
    "display(widgets.HBox([blocked_count, blocked_button]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在，这些按钮不会执行任何操作。 我们必须附加功能以将每个类别的图像保存到按钮的on_click事件中。 \n",
    "我们将保存Image图片的值小部件（而不是相机）的名称，因为它已经是压缩的JPEG格式！\n",
    "为了确保我们不重复任何文件名（即使在不同的机器上！），我们将在python中使用uuid包，该包定义了uuid1方法来生成\n",
    "唯一标识符。 该唯一标识符是根据诸如当前时间和机器地址之类的信息生成的。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from uuid import uuid1\n",
    "\n",
    "def save_snapshot(directory):\n",
    "    image_path = os.path.join(directory, str(uuid1()) + '.jpg')\n",
    "    with open(image_path, 'wb') as f:\n",
    "        f.write(image.value)\n",
    "\n",
    "def save_free():\n",
    "    global free_dir, free_count\n",
    "    save_snapshot(free_dir)\n",
    "    free_count.value = len(os.listdir(free_dir))\n",
    "    \n",
    "def save_blocked():\n",
    "    global blocked_dir, blocked_count\n",
    "    save_snapshot(blocked_dir)\n",
    "    blocked_count.value = len(os.listdir(blocked_dir))\n",
    "    \n",
    "# attach the callbacks, we use a 'lambda' function to ignore the\n",
    "# parameter that the on_click event would provide to our function\n",
    "# because we don't need it.\n",
    "free_button.on_click(lambda x: save_free())\n",
    "blocked_button.on_click(lambda x: save_blocked())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "很好！现在，上面的按钮应将图像保存到“ free”和“ blocked”目录中。您可以使用Jupyter Lab文件浏览器来查看这些文件！\n",
    "\n",
    "现在继续收集一些数据\n",
    "\n",
    "1.将机器人置于被阻止的场景中，然后按“add blocked”\n",
    "2.将机器人置于免费的场景中，然后按“add free”\n",
    "3.重复1、2\n",
    "\n",
    ">提醒：您可以通过右键单击单元格并单击``为输出创建新视图''将小部件移动到新窗口。或者，您可以重新显示它们\n",
    ">一起，如下所示\n",
    "\n",
    "以下是标记数据的一些技巧\n",
    "\n",
    "1.尝试不同的方向\n",
    "2.尝试不同的照明\n",
    "3.尝试各种物体/碰撞类型；墙壁，壁架，物体\n",
    "4.尝试不同的纹理地板/物体；图案，光滑，玻璃等\n",
    "\n",
    "最终，我们在现实世界中遇到的机器人场景中的数据越多，我们的防撞性能就会越好。这一点很重要\n",
    "以获得* varied *数据（如上述提示所述），而不仅仅是大量数据，但每个类至少需要100张图像（这不是一门科学，这里只是一个有用的提示）。但是不用担心，一旦开始，它就会非常快:)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "display(image)\n",
    "display(widgets.HBox([free_count, free_button]))\n",
    "display(widgets.HBox([blocked_count, blocked_button]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 接下来\n",
    "\n",
    "收集到足够的数据后，我们需要将该数据复制到我们的GPU台式机或云机上进行培训。 首先，我们可以调用以下* terminal *命令进行压缩\n",
    "我们的数据集文件夹放入一个* zip *文件中。\n",
    "\n",
    ">！ 前缀表示我们要以* shell *（或* terminal *）命令运行单元格。\n",
    "\n",
    ">以下zip命令中的-r标志表示* recursive *，以便我们包含所有嵌套文件； -q标志表示* quiet *，以便zip命令不会输出任何输出"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!zip -r -q dataset.zip dataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "您应该在Jupyter Lab文件浏览器中看到一个名为``dataset.zip''的文件。 您应该使用Jupyter Lab文件浏览器下载zip文件，方法是右键单击并选择``下载''。\n",
    "\n",
    "接下来，我们需要将此数据上传到我们的GPU台式机或云机（我们将其称为* host *），以训练防撞神经网络。 我们假设您已经设置好培训\n",
    "如JetBot WiKi中所述。 如果有的话，您可以导航到http：// <host_ip_address>：8888以打开在主机上运行的Jupyter Lab环境。 您需要在此处打开的笔记本称为“ collision_avoidance / train_model.ipynb”。\n",
    "\n",
    "因此，前往培训机器并按照那里的说明进行操作！ 训练完模型后，我们将返回到机器人Jupyter Lab环境，以使用该模型进行实时演示！"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
